package eo.application.hibernate.support;

import java.awt.Dialog;
import java.awt.Window;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.HibernateException;
import org.hibernate.SessionFactory;
import org.hibernate.classic.Session;
import org.hibernate.context.CurrentSessionContext;
import org.hibernate.engine.SessionFactoryImplementor;

public class TopLevelDialogSessionContext extends WindowAdapter implements
        CurrentSessionContext, WindowListener, ComponentListener {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    private final SessionFactoryImplementor sessionFactory;

    private final Map<Window, Session> sessions = Collections
            .synchronizedMap(new HashMap<Window, Session>());

    /**
     * C'tor required by contract.
     * 
     * @param sessionFactory
     *            the owning {@link SessionFactory}
     */
    public TopLevelDialogSessionContext(
            final SessionFactoryImplementor sessionFactory) {
        super();
        this.sessionFactory = sessionFactory;
    }

    public Session currentSession() throws HibernateException {
        return this.getCurrentSessionOrOpen();
    }

    private Window findSessionKey() {
        Window[] windows = Window.getWindows();
        for (Window window : windows) {
            if (window.isActive()) {
                if (window instanceof Dialog) {
                    return this.findTopLevelDialog((Dialog) window);
                }
                return window;
            }
        }
        return null;
    }

    /**
     * Finds the top level dialog (the one that has a Frame or <code>null</code>as
     * its parent) from the given dialog
     * 
     * @param dialog
     *            the window
     * @return {@link Dialog}
     */
    private Dialog findTopLevelDialog(final Dialog dialog) {
        if (dialog.getParent() == null
                || !(dialog.getParent() instanceof Dialog)) {
            return dialog;
        }
        return this.findTopLevelDialog((Dialog) dialog.getParent());
    }

    private Session getCurrentSessionOrOpen() {
        Window key = this.findSessionKey();
        if (key == null) {
            throw new IllegalStateException("No active window found!");
        }
        return this.getOrCreateSessionFor(key);
    }

    private synchronized Session getOrCreateSessionFor(final Window key) {
        Session session = this.sessions.get(key);
        if (session != null) {
            if (!session.isOpen()) {
                this.sessions.remove(key);
            } else {
                return session;
            }
        }
        session = this.sessionFactory.openSession();
        this.sessions.put(key, session);
        key.addWindowListener(this);
        key.addComponentListener(this);
        return session;
    }

    @Override
    public void windowClosing(final WindowEvent e) {
        Window window = e.getWindow();
        closeSessionForWindow(window);
    }

    private synchronized void closeSessionForWindow(Window window) {
        Session session = this.sessions.get(window);
        if (session == null) {
            return;
        }
        if (!session.isOpen()) {
            this.sessions.remove(window);
            return;
        }
        this.findLogger().info(
                "Closing sesion associated with window: " + window);
        if (session.isDirty() && session.getTransaction() != null
                && session.getTransaction().isActive()) {
            findLogger().warn(
                    "Transaction still active while closing a JDialog! "
                            + "Either commit or rollback "
                            + "the transaction before closing!");
            session.getTransaction().rollback();
        }
        session.close();
    }

    @Override
    public void windowClosed(WindowEvent e) {
        this.closeSessionForWindow(e.getWindow());
    }

    private Log findLogger() {
        return LogFactory.getLog(this.getClass());
    }

    public void componentHidden(ComponentEvent e) {
        if (e.getComponent() instanceof Window) {
            this.closeSessionForWindow((Window) e.getComponent());
        }
    }

    public void componentMoved(ComponentEvent e) {
    }

    public void componentResized(ComponentEvent e) {
    }

    public void componentShown(ComponentEvent e) {
    }

}
